#include "shell.h"

static int32_t cmd_switch(char* str)
{
	int32_t ret = 0;
	
	printf("execute cmd switch:%s\n", str);
	
	return ret;
}

shell_command_t commands[] = {
  { "switch", cmd_switch, "Change input channel" },
  { "udp", NULL, "UDP test" },
  { "tcp", NULL, "TCP test" },
  { "uart", NULL, "UART test" },
  { "can", NULL, "CAN test" },
  { "i2c", NULL, "I2C test" },
  { "send", NULL, "Send test" },
  { "read", NULL, "Read test" },
  { "write", NULL, "Write test" },
  { "close", NULL, "Close test interface" },
  { (char *)NULL, (rl_icpfunc_t *)NULL, (char *)NULL }
};

static char* command_generator(const char *text, int state)
{
    static int list_index, len;
    char *name = NULL;

    /*
    * If this is a new word to complete, initialize now. This includes
    * saving the length of TEXT for efficiency, and initializing the index
    * variable to 0.
    */
    if (!state) {
        list_index = 0;
        len = strlen(text);
    }

    /* Return the next name which partially matches from the command list. */
    name = commands[list_index].name;
    while(name) {
        list_index++;
    
        if(strncmp(name, text, len) == 0)
            return (strdup(name));

        name = commands[list_index].name;
    }

    return NULL;
}

static char** fileman_completion(const char *text, int start, int end)
{
    char **matches;

    matches = (char **)NULL;

    /*
    * If this word is at the start of the line, then it is a command
    * to complete. Otherwise it is the name of a file in the current
    * directory.
    */
    if(start == 0)
        matches = rl_completion_matches(text, command_generator);

    return (matches);
}

static char* stripwhite(char *str)
{
    register char *s, *t;
    
    for (s = str; whitespace(*s); s++)
        ;
    
    if (*s == 0)
        return (s);

    t = s + strlen(s) - 1;
    while (t > s && whitespace(*t))
        t--;

    *++t = '\0';

    return s;
}

static int execute_line(struct shell* pshell, char *line)
{
    register int i, j;
    shell_command_t *pcmd = NULL;
    char *word;
    
    cmd_object_t cmd_obj;

    /* Isolate the command word. */
    i = 0;
    while(line[i] && whitespace(line[i]))
        i++;
    word = line + i;

    while(line[i] && !whitespace(line[i]))
        i++;

    if(line[i])
        line[i++] = '\0';

    for (j = 0; commands[j].name; j++){
        if (strcmp (word, commands[j].name) == 0){
            pcmd = &commands[j];
        }
    }
    if(!pcmd) {
        fprintf(stderr, "%s: No such command.\n", word);
        return -1;
    }
    //sprintf(cmd_obj.cmd, "%s", word);
    cmd_obj.cmd = word;
    cmd_obj.param_nums = 0;
#if 1
    while(1){
        /* Get argument to command, if any. */
        while(whitespace(line[i]))
            i++;

        word = line + i;
        if(word[0] == 0){
            cmd_obj.param[cmd_obj.param_nums] = NULL;
            break;
        }else{
            cmd_obj.param[cmd_obj.param_nums] = word;
        }
        while(line[i] && !whitespace(line[i]))
            i++;
        if(line[i])
            line[i++] = '\0';

        cmd_obj.param_nums++;
    }
    pshell->op->trigger_notify(pshell, SHELL_EVENT_COMMANDS, &cmd_obj);
#else
    /* Get argument to command, if any. */
    while(whitespace(line[i]))
        i++;

    word = line + i;
    
    //sprintf(cmd_obj.param, "%s", word);
    cmd_obj.param = word;
    
    pshell->op->trigger_notify(pshell, SHELL_EVENT_COMMANDS, &cmd_obj);
#endif
    return 0;

    /* Call the function. */
    //return ((*(pcmd->func)) (word));
}

static void initialize_readline (void)
{
    /* Allow conditional parsing of the ~/.inputrc file. */
    rl_readline_name = ">";

    /* Tell the completer that we want a crack first. */
    rl_attempted_completion_function = fileman_completion;
}

static int32_t shell_process(struct shell* pshell)
{
	int32_t ret = 0;
	char *line;
	char *cmd;
	
	line = readline("shell $ ");
	if(!line){
		return -1;
	}
	
	cmd = stripwhite(line);
	if(*cmd){
        add_history(line);
		execute_line(pshell, cmd);
	}
	
	free(line);
	
	return ret;
}

static int32_t shell_init(struct shell* pshell)
{
    int32_t ret = 0;

    INIT_LIST_HEAD(&(pshell->head));
    lock_init(&(pshell->lock));
    lock_init(&(pshell->notify_lock));
    wait_init(&(pshell->wait));

    initialize_readline();
    
    return ret;
}

static int32_t shell_release(struct shell* pshell)
{
    /*
    free all sources
    ......
    */

    lock_destroy((&pshell->notify_lock));
    lock_destroy((&pshell->lock));
    wait_destroy((&pshell->wait));

    return 0;
}

static int32_t shell_register_notify(struct shell* pshell, int32_t event, shell_event_notify notify, void* object)
{
    struct shell_event_action* paction;
    if(!notify || (event <= SHELL_EVENT_NONE) || (event >= SHELL_EVENT_MAX)){
        return -EINVAL;
    }
    paction = (struct shell_event_action*)malloc(sizeof(struct shell_event_action));
    if(!paction){
        DBG(DBG_ERR,"malloc error\n");
        return -ENOMEM;
    }
    paction->notify = notify;
    paction->object = object;
    lock(&(pshell->notify_lock));
    paction->next = pshell->paction[event];
    pshell->paction[event] = paction;
    unlock(&(pshell->notify_lock));
    return 0;
}

static int32_t shell_unregister_notify(struct shell* pshell, int32_t event, void* object)
{
    struct shell_event_action *paction,* ptmp;
    if((event <= SHELL_EVENT_NONE) || (event >= SHELL_EVENT_MAX)){
        return -EINVAL;
    }
    lock(&(pshell->notify_lock));
    paction = pshell->paction[event];
    if(paction){
        if(paction->object == object){
            pshell->paction[event] = paction->next;
            free(paction);
        }else{
            while(paction->next){
                if(paction->next->object == object){
                    ptmp = paction->next;
                    paction->next = ptmp->next;
                    free(ptmp);
                    break;
                }
                paction = paction->next;
            }
        }
    }
    unlock(&(pshell->notify_lock));

    return 0;
}

static int32_t shell_trigger_notify(struct shell* pshell, int32_t event, void* context)
{
    struct shell_event_action* paction;
    if((event <= SHELL_EVENT_NONE) || (event >= SHELL_EVENT_MAX)){
        return -EINVAL;
    }
    lock(&(pshell->notify_lock));
    paction = pshell->paction[event];
    while(paction){
        paction->notify(pshell, event, paction->object, context);
        paction = paction->next;
    }
    unlock(&(pshell->notify_lock));

    return 0;
}

static struct shell_operation shell_op =
{
    .init = shell_init,
    .release = shell_release,
    
    .process = shell_process,

    .register_notify = shell_register_notify,
    .unregister_notify = shell_unregister_notify,
    .trigger_notify = shell_trigger_notify,
};

int32_t create_init_shell(struct shell** pshell)
{
    int32_t ret;
    struct shell* ptmp;
    (*pshell) = (struct shell*)malloc(sizeof(struct shell));
    if(!(*pshell)){
        DBG(DBG_ERR,"malloc error\n");
        return -ENOMEM;
    }
    ptmp = *pshell;
    memset(ptmp, 0, sizeof(struct shell));
    ptmp->op = &shell_op;
    ret = ptmp->op->init(ptmp);
    if(ret < 0){
        DBG(DBG_ERR,"init error\n");
        release_destroy_shell(ptmp);
        return ret;
    }
    return 0;
}

void release_destroy_shell(struct shell* pshell)
{
    if(pshell){
        pshell->op->release(pshell);
        free(pshell);
    }
}
        
